//------------------------------------------------------------------------------
// <copyright file="XmlDataDocument.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Xml {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.Versioning;
    using System.Xml.XPath;

    /// <devdoc>
    ///    <para>
    ///       Represents an entire document. An XmlDataDocument can contain XML
    ///       data or relational data (DataSet).
    ///    </para>
    /// </devdoc>
    [Obsolete("XmlDataDocument class will be removed in a future release.")]
    [System.Security.Permissions.HostProtectionAttribute(Synchronization=true)]
    public class XmlDataDocument: XmlDocument {
        DataSet dataSet;

        DataSetMapper mapper;
        internal Hashtable pointers;         // Hastable w/ all pointer objects used by this XmlDataDocument. Hashtable are guaranteed to work OK w/ one writer and mutiple readers, so as long as we guarantee
                                    // that there is at most one thread in AddPointer we are OK.
        int     countAddPointer;    // Approximate count of how many times AddPointer was called since the last time we removed the unused pointer objects from pointers hashtable.
        ArrayList columnChangeList;
        DataRowState rollbackState;

        bool fBoundToDataSet;       // true if our permanent event listeners are registered to receive DataSet events
        bool fBoundToDocument;      // true if our permanent event listeners are registered to receive XML events. Note that both fBoundToDataSet and fBoundToDataSet should be both true or false.
        bool fDataRowCreatedSpecial;    // true if our special event listener is registered to receive DataRowCreated events. Note that we either have special listeners subsribed or permanent ones (i.e. fDataRowCreatedSpecial and fBoundToDocument/fBoundToDataSet cannot be both true).
        bool ignoreXmlEvents;       // true if XML events should not be processed
        bool ignoreDataSetEvents;   // true if DataSet events should not be processed
        bool isFoliationEnabled;    // true if we should create and reveal the virtual nodes, false if we should reveal only the physical stored nodes
        bool optimizeStorage;       // false if we should only have foilated regions.
        ElementState autoFoliationState;    // When XmlBoundElement will foliate because of memeber functions, this will contain the foliation mode: usually this is
                                    // ElementState.StrongFoliation, however when foliation occurs due to DataDocumentNavigator operations (InsertNode for example),
                                    // it it usually ElementState.WeakFoliation
        bool fAssociateDataRow;     // if true, CreateElement will create and associate data rows w/ the newly created XmlBoundElement.
                                    // If false, then CreateElement will just create the XmlBoundElement nodes. This is usefull for Loading case,
                                    // when CreateElement is called by DOM.
        object foliationLock;
        internal const string XSI_NIL = "xsi:nil";
        internal const string XSI = "xsi";
        private bool bForceExpandEntity = false;
        internal XmlAttribute attrXml = null;
        internal bool bLoadFromDataSet = false;
        internal bool bHasXSINIL = false;

        internal void AddPointer( IXmlDataVirtualNode pointer ) {
            Debug.Assert( pointers.ContainsValue(pointer) == false );
            lock ( pointers ) {
                countAddPointer++;
                if ( countAddPointer >= 5 ) {   // 5 is choosed to be small enough to not affect perf, but high enough so we will not scan all the time
                    ArrayList al = new ArrayList();
                    foreach( DictionaryEntry entry in pointers ) {
                        IXmlDataVirtualNode temp = (IXmlDataVirtualNode)(entry.Value);
                        Debug.Assert( temp != null );
                        if ( ! temp.IsInUse() )
                            al.Add( temp );
                    }
                    for (int i = 0; i < al.Count; i++ ) {
                        pointers.Remove( al[ i ] );
                    }
                    countAddPointer = 0;
                }
                pointers[pointer] = pointer;
            }
        }

        [System.Diagnostics.Conditional("DEBUG")]
        internal void AssertPointerPresent( IXmlDataVirtualNode pointer ) {
#if DEBUG
                object val = pointers[pointer];
                if ( val != ( object ) pointer )
                    Debug.Assert( false );
#endif
        }
        // This function attaches the DataSet to XmlDataDocument
        // We also register a special listener (OnDataRowCreatedSpecial) to DataSet, so we know when we should setup all regular listeners (OnDataRowCreated, OnColumnChanging, etc).
        // We need to do this because of the following scenario:
        //  - XmlDataDocument doc = new XmlDataDocument();
        //  - DataSet ds = doc.DataSet;     // doc.DataSet creates a data-set, however does not sets-up the regular listeners.
        //  - ds.ReadXmlSchema();           // since there are regular listeners in doc that track ds schema changes, doc does not know about the new tables/columns/etc
        //  - ds.ReadXmlData();             // ds is now filled, however doc has no content (since there were no listeners for the new created DataRow's)
        // We can set-up listeners and track each change in schema, but it is more perf-friendly to do it laizily, all at once, when the first DataRow is created
        // (we rely on the fact that DataRowCreated is a DataSet wide event, rather than a DataTable event)
        private void AttachDataSet( DataSet ds ) {
            // You should not have already an associated dataset
            Debug.Assert( dataSet == null );
            Debug.Assert( ds != null );
            if ( ds.FBoundToDocument )
                throw new ArgumentException( Res.GetString(Res.DataDom_MultipleDataSet) );
            ds.FBoundToDocument = true;
            dataSet = ds;
            // Register the special listener to catch the first DataRow event(s)
            BindSpecialListeners();
        }

        // after loading, all detached DataRows are synchronized with the xml tree and inserted to their tables
        // or after setting the innerxml, synchronize the rows and if created new and detached, will be inserted.
        internal void SyncRows( DataRow parentRow, XmlNode node, bool fAddRowsToTable) {
            XmlBoundElement be = node as XmlBoundElement;
            if ( be != null ) {
                DataRow r = be.Row;
                if (r!=null && be.ElementState == ElementState.Defoliated)
                    return; //no need of syncRow

                if ( r != null ) {
                    // get all field values.
                    SynchronizeRowFromRowElement( be );

                    // defoliate if possible
                    be.ElementState = ElementState.WeakFoliation;
                    DefoliateRegion( be );

                    if ( parentRow != null )
                        SetNestedParentRow( r, parentRow );
                    if ( fAddRowsToTable && r.RowState == DataRowState.Detached )
                        r.Table.Rows.Add( r );
                    parentRow = r;
                }
            }

            // Attach all rows from children nodes
            for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling )
            	SyncRows( parentRow, child, fAddRowsToTable );
        }

        // All detached DataRows are synchronized with the xml tree and inserted to their tables.
        // Synchronize the rows and if created new and detached, will be inserted.
        internal void SyncTree( XmlNode node) {
            XmlBoundElement be = null;
            mapper.GetRegion( node, out be ) ;
            DataRow parentRow = null;
            bool fAddRowsToTable = IsConnected(node) ;

            if ( be != null ) {
                DataRow r = be.Row;
                if (r!=null && be.ElementState == ElementState.Defoliated)
                    return; //no need of syncRow

                if ( r != null ) {
                    // get all field values.
                    SynchronizeRowFromRowElement( be );

                    // defoliation will not be done on the node which is not RowElement, in case of node is externally being used
                    if ( node == be ) {
                         // defoliate if possible
                         be.ElementState = ElementState.WeakFoliation;
                         DefoliateRegion( be );
                    }

                    if ( fAddRowsToTable && r.RowState == DataRowState.Detached )
                        r.Table.Rows.Add( r );

                    parentRow = r;
                }
            }

            // Attach all rows from children nodes
            for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling )
            	SyncRows( parentRow, child, fAddRowsToTable );
        }

        internal ElementState AutoFoliationState {
            get { return this.autoFoliationState; }
            set { this.autoFoliationState = value; }
        }

        private void BindForLoad() {
            Debug.Assert( ignoreXmlEvents == true );
            ignoreDataSetEvents = true;
            mapper.SetupMapping( this, dataSet );
            if ( dataSet.Tables.Count > 0 ) {
                //at least one table
                LoadDataSetFromTree( );
            }
            BindListeners();
            ignoreDataSetEvents = false;
        }

        private void Bind( bool fLoadFromDataSet ) {
            // If we have a DocumentElement then it is illegal to call this func to load from data-set
            Debug.Assert( DocumentElement == null || ! fLoadFromDataSet );

            ignoreDataSetEvents = true;
            ignoreXmlEvents = true;

            // Do the mapping. This could be a successive mapping in case of this scenario: xd = XmlDataDocument( emptyDataSet ); xd.Load( "file.xml" );
            mapper.SetupMapping(this, dataSet);

            if ( DocumentElement != null ) {
                LoadDataSetFromTree();
                BindListeners();
            }
            else if ( fLoadFromDataSet ) {
                this.bLoadFromDataSet = true;
                LoadTreeFromDataSet( DataSet );
                BindListeners();
            }

            ignoreDataSetEvents = false;
            ignoreXmlEvents = false;
        }

        internal void Bind( DataRow r, XmlBoundElement e ) {
            r.Element = e;
            e.Row = r;
        }

        // Binds special listeners to catch the 1st data-row created. When the 1st DataRow is created, XmlDataDocument will automatically bind all regular listeners.
        private void BindSpecialListeners() {
            Debug.Assert( fDataRowCreatedSpecial == false );
            Debug.Assert( fBoundToDataSet == false && fBoundToDocument == false );
            dataSet.DataRowCreated += new DataRowCreatedEventHandler( this.OnDataRowCreatedSpecial );
            fDataRowCreatedSpecial = true;
        }
        private void UnBindSpecialListeners() {
            Debug.Assert( fDataRowCreatedSpecial == true );
            dataSet.DataRowCreated -= new DataRowCreatedEventHandler( this.OnDataRowCreatedSpecial );
            fDataRowCreatedSpecial = false;
        }

        private void BindListeners() {
            BindToDocument();
            BindToDataSet();
        }

        private void BindToDataSet() {
            // We could be already bound to DataSet in this scenario:
            //     xd = new XmlDataDocument( dataSetThatHasNoData ); xd.Load( "foo.xml" );
            // so we must not rebound again to it.
            if ( fBoundToDataSet ) {
                Debug.Assert( dataSet != null );
                return;
            }

            // Unregister the DataRowCreatedSpecial notification
            if ( fDataRowCreatedSpecial )
                UnBindSpecialListeners();

            dataSet.Tables.CollectionChanging += new CollectionChangeEventHandler( this.OnDataSetTablesChanging );
            dataSet.Relations.CollectionChanging += new CollectionChangeEventHandler( this.OnDataSetRelationsChanging );
            dataSet.DataRowCreated += new DataRowCreatedEventHandler( this.OnDataRowCreated );
            dataSet.PropertyChanging += new PropertyChangedEventHandler( this.OnDataSetPropertyChanging );

            //this is the hack for this release, should change it in the future
            dataSet.ClearFunctionCalled += new DataSetClearEventhandler( this.OnClearCalled );

            if ( dataSet.Tables.Count > 0 ) {
                foreach( DataTable t in dataSet.Tables ) {
                    BindToTable( t );
                }
            }

            foreach ( DataRelation rel in dataSet.Relations ) {
                rel.PropertyChanging += new PropertyChangedEventHandler( this.OnRelationPropertyChanging );
            }
            fBoundToDataSet = true;
        }

        private void BindToDocument() {
            if ( !fBoundToDocument ) {
                NodeInserting +=  new XmlNodeChangedEventHandler( this.OnNodeInserting ) ;
                NodeInserted +=  new XmlNodeChangedEventHandler( this.OnNodeInserted ) ;
                NodeRemoving +=  new XmlNodeChangedEventHandler( this.OnNodeRemoving ) ;
                NodeRemoved +=  new XmlNodeChangedEventHandler( this.OnNodeRemoved ) ;
                NodeChanging +=  new XmlNodeChangedEventHandler( this.OnNodeChanging ) ;
                NodeChanged +=  new XmlNodeChangedEventHandler( this.OnNodeChanged ) ;
                fBoundToDocument = true;
            }
        }

        private void BindToTable( DataTable t ) {
//            t.ColumnChanging  += new DataColumnChangeEventHandler( this.OnColumnChanging );
            t.ColumnChanged   += new DataColumnChangeEventHandler( this.OnColumnChanged );
            t.RowChanging     += new DataRowChangeEventHandler( this.OnRowChanging );
            t.RowChanged      += new DataRowChangeEventHandler( this.OnRowChanged );
            t.RowDeleting     += new DataRowChangeEventHandler( this.OnRowChanging);
            t.RowDeleted      += new DataRowChangeEventHandler( this.OnRowChanged );
            t.PropertyChanging += new PropertyChangedEventHandler( this.OnTablePropertyChanging );
            t.Columns.CollectionChanging += new CollectionChangeEventHandler( this.OnTableColumnsChanging );

            foreach( DataColumn col in t.Columns ) {
                // Hook column properties changes, so we can react properly to ROM changes.
                col.PropertyChanging += new PropertyChangedEventHandler( this.OnColumnPropertyChanging );
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Creates an element with the specified Prefix, LocalName, and
        ///       NamespaceURI.
        ///    </para>
        /// </devdoc>
        public override XmlElement CreateElement( string prefix, string localName, string namespaceURI) {
            //

            // There are three states for the document:
            //  - special listeners ON, no permananent listeners: this is when the data doc was created w/o any dataset, and the 1st time a new row/element
            //    is created we should subscribe the permenent listeners.
            //  - special listeners OFF, permanent listeners ON: this is when the data doc is loaded (from dataset or XML file) and synchronization takes place.
            //  - special listeners OFF, permanent listeners OFF: this is then the data doc is LOADING (from dataset or XML file) - the synchronization is done by code,
            //    not based on listening to events.
#if DEBUG
            // Cannot have both special and permananent listeners ON
            if ( fDataRowCreatedSpecial )
                Debug.Assert( (fBoundToDataSet == false) && (fBoundToDocument == false) );
            // fBoundToDataSet and fBoundToDocument should have the same value
            Debug.Assert( fBoundToDataSet ? fBoundToDocument : (! fBoundToDocument) );
#endif
            if ( prefix == null )
                prefix = String.Empty;
            if ( namespaceURI == null )
                namespaceURI = String.Empty;

            if ( ! fAssociateDataRow ) {
                // Loading state: create just the XmlBoundElement: the LoadTreeFromDataSet/LoadDataSetFromTree will take care of synchronization
                return new XmlBoundElement( prefix, localName, namespaceURI, this );
            }

            // This is the 1st time an element is beeing created on an empty XmlDataDocument - unbind special listeners, bind permanent ones and then go on w/
            // creation of this element
            EnsurePopulatedMode();
            Debug.Assert( fDataRowCreatedSpecial == false );

            // Loaded state: create a DataRow, this in turn will create and associate the XmlBoundElement, which we will return.
            DataTable dt = mapper.SearchMatchingTableSchema( localName, namespaceURI );
            if ( dt != null ) {
                DataRow row = dt.CreateEmptyRow();
                // We need to make sure all fields are DBNull
                foreach( DataColumn col in dt.Columns ) {
                    if ( col.ColumnMapping != MappingType.Hidden )
                        SetRowValueToNull( row, col );
                }
                XmlBoundElement be = row.Element;
                Debug.Assert( be != null );
                be.Prefix = prefix;
                return be;
            }
            // No matching table schema for this element: just create the element
            return new XmlBoundElement( prefix, localName, namespaceURI, this );
        }

        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override XmlEntityReference CreateEntityReference(String name) {
            throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_EntRef ) );
        }

        /// <devdoc>
        ///    <para>Gets a DataSet that provides a relational representation of the data in this
        ///       XmlDataDocument.</para>
        /// </devdoc>
        public DataSet DataSet {
            get {
                return dataSet;
            }
        }

        private void DefoliateRegion( XmlBoundElement rowElem ) {
            // You must pass a row element (which s/b associated w/ a DataRow)
            Debug.Assert( rowElem.Row != null );

            if ( !optimizeStorage )
                return;

            if ( rowElem.ElementState != ElementState.WeakFoliation )
                return;

            if ( !mapper.IsRegionRadical( rowElem ) ) {
                //Console.WriteLine("Region is not radical: "+rowElem.GetHashCode());
                return;
            }

            //Console.WriteLine("Defoliating Region: " + rowElem.GetHashCode());

            bool saveIgnore = IgnoreXmlEvents;
            IgnoreXmlEvents = true;

            rowElem.ElementState = ElementState.Defoliating;

            try {
                // drop all attributes
                rowElem.RemoveAllAttributes();

                XmlNode node = rowElem.FirstChild;
                while ( node != null ) {
                    XmlNode next = node.NextSibling;

                    XmlBoundElement be = node as XmlBoundElement;
                    if ( be != null && be.Row != null )
                        break;

                    // The node must be mapped to a column (since the region is radically structured)
                    Debug.Assert( mapper.GetColumnSchemaForNode( rowElem, node ) != null );
                    rowElem.RemoveChild( node );

                    node = next;
                }
#if DEBUG
                // All subsequent siblings must be sub-regions
                for ( ; node != null; node = node.NextSibling ) {
                    Debug.Assert( (node is XmlBoundElement) && (((XmlBoundElement)node).Row != null) );
                }
#endif

                rowElem.ElementState = ElementState.Defoliated;
            }
            finally {
                IgnoreXmlEvents = saveIgnore;
            }
        }

        private XmlElement EnsureDocumentElement() {
            XmlElement docelem = DocumentElement;
            if ( docelem == null ) {
                string docElemName = XmlConvert.EncodeLocalName( this.DataSet.DataSetName );
                if ( docElemName == null || docElemName.Length == 0 )
                    docElemName = "Xml";
                string ns = this.DataSet.Namespace;
                if ( ns == null )
                    ns = String.Empty;
                docelem = new XmlBoundElement( string.Empty, docElemName, ns, this );
                AppendChild( docelem );
            }

            return docelem;
        }
        private XmlElement EnsureNonRowDocumentElement() {
            XmlElement docElem = this.DocumentElement;
            if ( docElem == null )
                return EnsureDocumentElement();

            DataRow rowDocElem = GetRowFromElement( docElem );
            if ( rowDocElem == null )
                return docElem;

            return DemoteDocumentElement();
        }
        private XmlElement DemoteDocumentElement() {
            // Changes of Xml here should not affect ROM
            Debug.Assert( this.ignoreXmlEvents == true );
            // There should be no reason to call this function if docElem is not a rowElem
            Debug.Assert( this.GetRowFromElement( this.DocumentElement ) != null );

            // Remove the DocumentElement and create a new one
            XmlElement oldDocElem = this.DocumentElement;
            RemoveChild( oldDocElem );
            XmlElement docElem = EnsureDocumentElement();
            docElem.AppendChild( oldDocElem );
            // We should have only one child now
            Debug.Assert( docElem.LastChild == docElem.FirstChild );
            return docElem;
        }
        // This function ensures that the special listeners are un-subscribed, the permanent listeners are subscribed and
        // CreateElement will attach DataRows to newly created XmlBoundElement.
        // It should be called when we have special listeners hooked and we need to change from the special-listeners mode to the
        // populated/permanenet mode where all listeners are corectly hooked up and the mapper is correctly set-up.
        private void EnsurePopulatedMode() {
            // Unbind special listeners, bind permanent ones, setup the mapping, etc
#if DEBUG
            bool fDataRowCreatedSpecialOld = fDataRowCreatedSpecial;
            bool fAssociateDataRowOld = fAssociateDataRow;
#endif
            if ( fDataRowCreatedSpecial ) {
                UnBindSpecialListeners();
                // If a special listener was ON, we should not have had an already set-up mapper or permanent listeners subscribed
                Debug.Assert( ! mapper.IsMapped() );
                Debug.Assert( ! fBoundToDocument );
                Debug.Assert( ! fBoundToDataSet );

                mapper.SetupMapping( this, dataSet);
                BindListeners();

                // CreateElement should now create associate DataRows w/ new XmlBoundElement nodes
                // We should do this ONLY if we switch from special listeners to permanent listeners. The reason is
                // that DataDocumentNavigator wants to put XmlDataDocument in a batch mode, where CreateElement will just
                // create a XmlBoundElement (see DataDocumentNavigator.CloneTree)
                fAssociateDataRow = true;
            }

            Debug.Assert( fDataRowCreatedSpecial == false );
            Debug.Assert( mapper.IsMapped() );
            Debug.Assert( fBoundToDataSet && fBoundToDocument );
#if DEBUG
            // In case we EnsurePopulatedMode was called on an already populated mode, we should NOT change fAssociateDataRow
            if ( fDataRowCreatedSpecialOld == false )
                Debug.Assert( fAssociateDataRowOld == fAssociateDataRow );
#endif
        }

        // Move regions that are marked in ROM as nested children of row/rowElement as last children in XML fragment
        private void FixNestedChildren(DataRow row, XmlElement rowElement) {
            foreach( DataRelation dr in GetNestedChildRelations(row) ) {
                foreach( DataRow r in row.GetChildRows(dr) ) {
                    XmlElement childElem = r.Element;
                    // childElem can be null when we create XML from DataSet (XmlDataDocument( DataSet ) is called) and we insert rowElem of the parentRow before
                    // we insert the rowElem of children rows.
                    if ( childElem != null ) {
#if DEBUG
                        bool fIsChildConnected = IsConnected( childElem );
#endif
                        if ( childElem.ParentNode != rowElement ) {
                            childElem.ParentNode.RemoveChild( childElem );
                            rowElement.AppendChild( childElem );
                        }
#if DEBUG
                        // We should not have changed the connected/disconnected state of the node (since the row state did not change)
                        Debug.Assert( fIsChildConnected == IsConnected( childElem ) );
                        Debug.Assert( IsRowLive( r ) ? IsConnected( childElem ) : ! IsConnected( childElem ) );
#endif
                    }
                }
            }
        }

        // This function accepts node params that are not row-elements. In this case, calling this function is a no-op
        internal void Foliate( XmlBoundElement node, ElementState newState ) {

            Debug.Assert( newState == ElementState.WeakFoliation || newState == ElementState.StrongFoliation );
#if DEBUG
            // If we want to strong foliate one of the non-row-elem in a region, then the region MUST be strong-foliated (or there must be no region)
            // Do this only when we are not loading
            if ( IsFoliationEnabled ) {
                if( newState == ElementState.StrongFoliation && node.Row == null ) {
                    XmlBoundElement rowElem;
                    ElementState rowElemState = ElementState.None;
                    if ( mapper.GetRegion( node, out rowElem ) ) {
                        rowElemState = rowElem.ElementState;
                        Debug.Assert( rowElemState == ElementState.StrongFoliation || rowElemState == ElementState.WeakFoliation );
                    }
                    // Add a no-op, so we can still debug in the assert fails

#pragma warning disable 1717 // assignment to self
                    rowElemState = rowElemState;
#pragma warning restore 1717
                }
            }
#endif

            if ( IsFoliationEnabled ) {
                if ( node.ElementState == ElementState.Defoliated ) {
                    ForceFoliation( node, newState );
                }
                else if ( node.ElementState == ElementState.WeakFoliation && newState == ElementState.StrongFoliation ) {
                    // Node must be a row-elem
                    Debug.Assert( node.Row != null );
                    node.ElementState = newState;
                }
            }
        }

        private void Foliate( XmlElement element ) {
            if ( element is XmlBoundElement )
                ((XmlBoundElement)element).Foliate( ElementState.WeakFoliation );
        }

        // Foliate rowElement region if there are DataPointers that points into it
        private void FoliateIfDataPointers( DataRow row, XmlElement rowElement ) {
            if ( !IsFoliated( rowElement) && HasPointers( rowElement ) ) {
                bool wasFoliationEnabled = IsFoliationEnabled;
                IsFoliationEnabled = true;
                try {
                    Foliate( rowElement );
                }
                finally {
                    IsFoliationEnabled = wasFoliationEnabled;
                }
            }
        }

        private void EnsureFoliation( XmlBoundElement rowElem, ElementState foliation ) {
            if ( rowElem.IsFoliated ) //perf reason, avoid unecessary lock.
                return;
            ForceFoliation( rowElem, foliation);
        }

        private void ForceFoliation( XmlBoundElement node, ElementState newState ) {
            lock ( this.foliationLock ) {
                if ( node.ElementState != ElementState.Defoliated )
                    // The region was foliated by an other thread while this thread was locked
                    return;

                // Node must be a row-elem associated w/ a non-deleted row
                Debug.Assert( node.Row != null );
                Debug.Assert( node.Row.RowState != DataRowState.Deleted );

                node.ElementState = ElementState.Foliating;

                bool saveIgnore = IgnoreXmlEvents;
                IgnoreXmlEvents = true;

                try {
                    XmlNode priorNode = null;
                    DataRow row = node.Row;

                    // create new attrs & elements for row
                    // For detached rows: we are in sync w/ temp values
                    // For non-detached rows: we are in sync w/ the current values
                    // For deleted rows: we never sync
                    DataRowVersion rowVersion = ( row.RowState == DataRowState.Detached ) ? DataRowVersion.Proposed : DataRowVersion.Current;
                    foreach( DataColumn col in row.Table.Columns ) {
                        if ( !IsNotMapped(col) ) {
                            object value = row[col, rowVersion];

                            if ( ! Convert.IsDBNull( value ) ) {
                                if ( col.ColumnMapping == MappingType.Attribute ) {
                                    node.SetAttribute( col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml( value ) );
                                }
                                else {
                                    XmlNode newNode = null;
                                    if ( col.ColumnMapping == MappingType.Element ) {
                                        newNode = new XmlBoundElement( string.Empty, col.EncodedColumnName, col.Namespace, this );
                                        newNode.AppendChild( CreateTextNode( col.ConvertObjectToXml( value ) ) );
                                        if ( priorNode != null ) {
                                            node.InsertAfter(newNode, priorNode);
                                        }
                                        else if ( node.FirstChild != null ) {
                                            node.InsertBefore( newNode, node.FirstChild );
                                        }
                                        else {
                                            node.AppendChild( newNode );
                                        }
                                        priorNode = newNode;
                                    }
                                    else {
                                        Debug.Assert( col.ColumnMapping == MappingType.SimpleContent );
                                        newNode = CreateTextNode( col.ConvertObjectToXml( value ) );
                                        if ( node.FirstChild != null )
                                            node.InsertBefore( newNode, node.FirstChild );
                                        else
                                            node.AppendChild( newNode );
                                        if ( priorNode == null )
                                            priorNode = newNode;
                                    }
                                }
                            }
                            else {
                                if ( col.ColumnMapping == MappingType.SimpleContent ) {
                                    XmlAttribute attr = CreateAttribute( XSI, Keywords.XSI_NIL, Keywords.XSINS );
                                    attr.Value = Keywords.TRUE;
                                    node.SetAttributeNode( attr );
                                    this.bHasXSINIL = true;
                                }
                            }
                        }
                    }
                }
                finally {
                    IgnoreXmlEvents = saveIgnore;
                    node.ElementState = newState;
                }
                // update all live pointers
                OnFoliated( node );
            }
        }

        //Determine best radical insert position for inserting column elements
        private XmlNode GetColumnInsertAfterLocation( DataRow row, DataColumn col, XmlBoundElement rowElement ) {
            XmlNode prev = null;
            XmlNode node = null;

            // text only columns appear first
            if ( IsTextOnly( col ) )
                return null;

            // insert location must be after free text
            for ( node = rowElement.FirstChild; node != null; prev = node, node = node.NextSibling ) {
                if ( !IsTextLikeNode( node ) )
                    break;
            }

            for ( ; node != null; prev = node, node = node.NextSibling ) {
                // insert location must be before any non-element nodes
                if ( node.NodeType != XmlNodeType.Element )
                    break;
                XmlElement e = node as XmlElement;

                // insert location must be before any non-mapped elements or separate regions
                if ( mapper.GetRowFromElement( e ) != null )
                    break;

                object schema = mapper.GetColumnSchemaForNode( rowElement, node );
                if ( schema == null || !(schema is DataColumn) )
                    break;

                // insert location must be before any columns logically after this column
                if ( ((DataColumn)schema).Ordinal > col.Ordinal )
                    break;
            }

            return prev;
        }

        private ArrayList GetNestedChildRelations(DataRow row) {
            ArrayList list = new ArrayList();

            foreach( DataRelation r in row.Table.ChildRelations ) {
                if ( r.Nested )
                    list.Add(r);
            }

            return list;
        }

        private DataRow GetNestedParent(DataRow row) {
            DataRelation relation = GetNestedParentRelation(row);
            if ( relation != null )
                return row.GetParentRow(relation);
            return null;
        }

        private static DataRelation GetNestedParentRelation( DataRow row ) {
            DataRelation [] relations = row.Table.NestedParentRelations;
            if (relations.Length == 0)
                return null;
            return relations[0];
        }

        private DataColumn GetTextOnlyColumn( DataRow row ) {
#if DEBUG
            {
                // Make sure there is at most only one text column, and the text column (if present) is the one reported by row.Table.XmlText
                DataColumnCollection columns = row.Table.Columns;
                int cCols = columns.Count;
                int cTextCols = 0;
                for ( int iCol = 0; iCol < cCols; iCol++ ) {
                    DataColumn c = columns[iCol];
                    if ( IsTextOnly( c ) ) {
                        Debug.Assert( c == row.Table.XmlText );
                        ++cTextCols;
                    }
                }
                Debug.Assert( cTextCols == 0 || cTextCols == 1 );
                if ( cTextCols == 0 )
                    Debug.Assert( row.Table.XmlText == null );
            }
#endif
            return row.Table.XmlText;
        }

        /// <devdoc>
        ///    <para>
        ///       <SPAN>Retrieves the
        ///          DataRow associated with the specified XmlElement.</SPAN>
        ///       </para>
        ///    </devdoc>
        public DataRow GetRowFromElement( XmlElement e ) {
            return mapper.GetRowFromElement( e );
        }

        private XmlNode GetRowInsertBeforeLocation(DataRow row, XmlElement rowElement, XmlNode parentElement) {
            DataRow refRow = row;
            int i = 0;
            int pos;

            // Find position
            // int pos = row.Table.Rows[row];
            for (i = 0; i < row.Table.Rows.Count; i++)
                if (row == row.Table.Rows[i])
                    break;
            pos = i;

            DataRow parentRow = GetNestedParent(row);
            for (i = pos + 1; i < row.Table.Rows.Count; i++) {
                refRow = row.Table.Rows[i];
                if (GetNestedParent(refRow) == parentRow && GetElementFromRow(refRow).ParentNode == parentElement)
                    break;
            }

            if (i < row.Table.Rows.Count)
                return GetElementFromRow(refRow);
            else
                return(XmlNode) null;
        }


        /// <devdoc>
        ///    <para><SPAN>Retrieves
        ///       the XmlElement associated with the specified DataRow.</SPAN></para>
        /// </devdoc>
        public XmlElement GetElementFromRow( DataRow r ) {
            XmlBoundElement be = r.Element;
            //
            Debug.Assert( be != null );
            return be;
        }

        internal bool HasPointers( XmlNode node ) {
            while ( true ) {
                try {
                    if ( pointers.Count > 0 ) {
                        object pointer = null;
                        foreach( DictionaryEntry entry in pointers ) {
                            pointer = entry.Value;
                            Debug.Assert( pointer != null );
                            if ( ((IXmlDataVirtualNode)pointer).IsOnNode( node ) )
                                return true;
                        }
                    }
                    return false;
                }
                catch (Exception e) {
                    // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop.
                    // Solution is to re-try HasPointers.
                    // 
                    if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) {
                        throw;
                    }
                }
            }
            //should never get to this point due to while (true) loop
        }

        internal bool IgnoreXmlEvents {
            get { return ignoreXmlEvents;}
            set { ignoreXmlEvents = value;}
        }

        internal bool IgnoreDataSetEvents {
            get { return this.ignoreDataSetEvents; }
            set { this.ignoreDataSetEvents = value; }
        }

        private bool IsFoliated( XmlElement element ) {
            if ( element is XmlBoundElement ) {
                return((XmlBoundElement)element).IsFoliated;
            }

            return true;
        }
        private bool IsFoliated( XmlBoundElement be ) {
            return be.IsFoliated;
        }

        internal bool IsFoliationEnabled {
            get { return isFoliationEnabled; }
            set { isFoliationEnabled = value; }
        }

        // This creates a tree and synchronize ROM w/ the created tree.
        // It requires the populated mode to be on - in case we are not in populated mode, it will make the XmlDataDocument be in populated mode.
        // It takes advantage of the fAssociateDataRow flag for populated mode, which allows creation of XmlBoundElement w/o associating DataRow objects.
        internal XmlNode CloneTree( DataPointer other ) {
            EnsurePopulatedMode();

            bool oldIgnoreDataSetEvents = ignoreDataSetEvents;
            bool oldIgnoreXmlEvents     = ignoreXmlEvents;
            bool oldFoliationEnabled    = IsFoliationEnabled;
            bool oldAssociateDataRow    = fAssociateDataRow;

            // Caller should ensure that the EnforceConstraints == false. See 60486 for more info about why this was changed from DataSet.EnforceConstraints = false to an assert.
            Debug.Assert( DataSet.EnforceConstraints == false );
            XmlNode newNode;

            try {
                ignoreDataSetEvents = true;
                ignoreXmlEvents     = true;
                IsFoliationEnabled  = false;
                fAssociateDataRow   = false;

                // Create the diconnected tree based on the other navigator
                newNode = CloneTreeInternal( other );
                Debug.Assert( newNode != null );

                // Synchronize DataSet from XML
                LoadRows( null, newNode );
                SyncRows( null, newNode, false );
            }
            finally {
                ignoreDataSetEvents = oldIgnoreDataSetEvents;
                ignoreXmlEvents     = oldIgnoreXmlEvents;
                IsFoliationEnabled  = oldFoliationEnabled;
                fAssociateDataRow   = oldAssociateDataRow;
            }
            return newNode;
        }

        private XmlNode CloneTreeInternal( DataPointer other ) {
            Debug.Assert( ignoreDataSetEvents == true );
            Debug.Assert( ignoreXmlEvents == true );
            Debug.Assert( IsFoliationEnabled == false );

            // Create the diconnected tree based on the other navigator
            XmlNode newNode = CloneNode( other );

            DataPointer dp = new DataPointer( other );
            try {
                dp.AddPointer();
                if (newNode.NodeType == XmlNodeType.Element) {
                    int cAttributes = dp.AttributeCount;
                    for ( int i = 0; i < cAttributes; i++ ) {
                        dp.MoveToOwnerElement();
                        if( dp.MoveToAttribute(i) ) {
                            newNode.Attributes.Append( (XmlAttribute)CloneTreeInternal(dp) );
                        }
                    }

                    dp.MoveTo( other );
                }

                // 
                for ( bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling() )
                    newNode.AppendChild( CloneTreeInternal( dp ) );
            }
            finally {
                dp.SetNoLongerUse();
            }

            return newNode;
        }

        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override XmlNode CloneNode( bool deep ) {
            XmlDataDocument clone = (XmlDataDocument)(base.CloneNode(false));
            clone.Init(this.DataSet.Clone());

            clone.dataSet.EnforceConstraints = this.dataSet.EnforceConstraints;
            Debug.Assert( clone.FirstChild == null );
            if ( deep ) {
                DataPointer dp = new DataPointer( this, this );
                try {
                    dp.AddPointer();
                    for ( bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling() ) {
                        XmlNode cloneNode;
                        if ( dp.NodeType == XmlNodeType.Element )
                            cloneNode = clone.CloneTree( dp );
                        else
                            cloneNode = clone.CloneNode( dp );
                        clone.AppendChild( cloneNode );
                    }
                }
                finally{
                    dp.SetNoLongerUse();
                }
            }

            return clone;
        }

        private XmlNode CloneNode( DataPointer dp ) {
            switch (dp.NodeType) {
                //for the nodes without value and have no children
                case XmlNodeType.DocumentFragment:
                    return this.CreateDocumentFragment();
                case XmlNodeType.DocumentType:
                    //
                    return this.CreateDocumentType( dp.Name, dp.PublicId, dp.SystemId, dp.InternalSubset );
                case XmlNodeType.XmlDeclaration:
                    return this.CreateXmlDeclaration( dp.Version, dp.Encoding, dp.Standalone );
                //case XmlNodeType.Notation: -- notation should not be able to be accessed by XmlNavigator
                    //return this.CreateNotation(dp.Name, dp.PublicId, dp.SystemId );

                //for the nodes with value but no children
                case XmlNodeType.Text:
                    return this.CreateTextNode( dp.Value );
                case XmlNodeType.CDATA:
                    return this.CreateCDataSection( dp.Value );
                case XmlNodeType.ProcessingInstruction:
                    return this.CreateProcessingInstruction( dp.Name, dp.Value );
                case XmlNodeType.Comment:
                    return this.CreateComment( dp.Value );
                case XmlNodeType.Whitespace:
                    return this.CreateWhitespace( dp.Value );
                case XmlNodeType.SignificantWhitespace:
                    return this.CreateSignificantWhitespace( dp.Value );
                //for the nodes that don't have values, but might have children -- only clone the node and leave the children untouched
                case XmlNodeType.Element:
                    return this.CreateElement(dp.Prefix, dp.LocalName, dp.NamespaceURI);
                case XmlNodeType.Attribute:
                    return this.CreateAttribute(dp.Prefix, dp.LocalName, dp.NamespaceURI);
                case XmlNodeType.EntityReference:
                    return this.CreateEntityReference( dp.Name );
            }
            throw new InvalidOperationException( Res.GetString(Res.DataDom_CloneNode, dp.NodeType.ToString() ) );
        }

        internal static bool IsTextLikeNode( XmlNode n ) {
            switch ( n.NodeType ) {
                case XmlNodeType.Text:
                case XmlNodeType.CDATA:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                    return true;

                case XmlNodeType.EntityReference:
                    Debug.Assert( false );
                    return false;

                default:
                    return false;
            }
        }

        internal bool IsNotMapped( DataColumn c ) {
            return DataSetMapper.IsNotMapped( c );
        }

        private bool IsSame( DataColumn c, int recNo1, int recNo2 ) {
            if ( c.Compare( recNo1, recNo2 ) == 0 )
                return true;

            return false;
        }

        internal bool IsTextOnly( DataColumn c ) {
            return c.ColumnMapping == MappingType.SimpleContent;
        }


        /// <devdoc>
        ///    <para>Loads the XML document from the specified file.</para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public override void Load(string filename) {
            this.bForceExpandEntity = true;
            base.Load( filename );
            this.bForceExpandEntity = false;
        }

        /// <devdoc>
        ///    <para>Loads the XML document from the specified Stream.</para>
        /// </devdoc>
        public override void Load( Stream inStream ) {
            this.bForceExpandEntity = true;
            base.Load( inStream );
            this.bForceExpandEntity = false;
        }

        /// <devdoc>
        ///    <para>Loads the XML document from the specified TextReader.</para>
        /// </devdoc>
        public override void Load( TextReader txtReader ) {
            this.bForceExpandEntity = true;
            base.Load( txtReader );
            this.bForceExpandEntity = false;
        }

        /// <devdoc>
        ///    <para>Loads the XML document from the specified XmlReader.</para>
        /// </devdoc>
        public override void Load( XmlReader reader ) {
            if ( this.FirstChild != null )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_MultipleLoad ) );

            try {
                ignoreXmlEvents = true;

                // Unhook the DataRowCreatedSpecial listener, since we no longer base on the first created DataRow to do the Bind
                if ( fDataRowCreatedSpecial )
                    UnBindSpecialListeners();

                // We should NOT create DataRow objects when calling XmlDataDocument.CreateElement
                fAssociateDataRow = false;
                // Foliation s/b disabled
                isFoliationEnabled = false;

                //now if we load from file we need to set the ExpandEntity flag to ExpandEntities
                if ( this.bForceExpandEntity ) {
                    Debug.Assert( reader is XmlTextReader );
                    ((XmlTextReader)reader).EntityHandling = EntityHandling.ExpandEntities;
                }
                base.Load( reader );
                BindForLoad();
            }
            finally {
                ignoreXmlEvents = false;
                isFoliationEnabled = true;
                autoFoliationState = ElementState.StrongFoliation;
                fAssociateDataRow = true;
            }
        }

        private void LoadDataSetFromTree() {
            ignoreDataSetEvents = true;
            ignoreXmlEvents = true;
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;
            bool saveEnforce = dataSet.EnforceConstraints;
            dataSet.EnforceConstraints = false;

            try {
                Debug.Assert( DocumentElement != null );
                LoadRows( null, DocumentElement );
                SyncRows( null, DocumentElement, true );

                dataSet.EnforceConstraints = saveEnforce;
            }
            finally {
                ignoreDataSetEvents = false;
                ignoreXmlEvents = false;
                IsFoliationEnabled = wasFoliationEnabled;
            }
        }

        private void LoadTreeFromDataSet( DataSet ds ) {
            ignoreDataSetEvents = true;
            ignoreXmlEvents = true;
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;
            this.fAssociateDataRow = false;

            DataTable [] orderedTables = OrderTables(ds); // this  is to fix WebData 103397
            // problem is after we add support for Namespace  for DataTable, when infering we do not guarantee that table would be 
            // in the same sequence that they were in XML because of namespace, some would be on different schema, so since they
            // wont be in the same sequence as in XML, we may end up with having a child table, before its parent (which is not doable
            // with XML; and this happend because they are in different namespace)
            // this kind of problems are known and please see comment in "OnNestedParentChange"
            // so to fix it in general, we try to iterate over ordered tables instead of going over all tables in DataTableCollection with their own sequence

            try {
                for(int i = 0; i < orderedTables.Length; i++) {
                    DataTable t = orderedTables[i];
                    foreach( DataRow r in t.Rows ) {
                        Debug.Assert( r.Element == null );
                        XmlBoundElement rowElem = AttachBoundElementToDataRow( r );
                        // 
                        switch ( r.RowState ) {
                        case DataRowState.Added:
                        case DataRowState.Unchanged:
                        case DataRowState.Modified:
                            // 
                            OnAddRow( r );
                            break;
                        case DataRowState.Deleted:
                            // Nothing to do (the row already has an associated element as a fragment
                            break;
                        case DataRowState.Detached:
                            // We should not get rows in this state
                            Debug.Assert( false );
                            break;
                        default:
                            // Unknown row state
                            Debug.Assert( false );
                            break;
                        }
                    }
                }
            }
            finally {
                ignoreDataSetEvents = false;
                ignoreXmlEvents = false;
                IsFoliationEnabled = wasFoliationEnabled;
                this.fAssociateDataRow = true;
            }
        }

        // load all data from tree structre into datarows
        private void LoadRows( XmlBoundElement rowElem, XmlNode node ) {
            Debug.Assert( node != null );

            XmlBoundElement be = node as XmlBoundElement;
            if ( be != null ) {
                DataTable dt = mapper.SearchMatchingTableSchema( rowElem, be );

                if ( dt != null ) {
                    DataRow r = GetRowFromElement( be );
                    Debug.Assert( r == null );
                    // If the rowElement was just created and has an un-initialized
                    if ( be.ElementState == ElementState.None )
                        be.ElementState = ElementState.WeakFoliation;
                    r = dt.CreateEmptyRow();
                    Bind( r, be );

                    // the region rowElem is now be
                    Debug.Assert( be.Row != null );
                    rowElem = be;
                }
            }
            // recurse down for children
            for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling )
                LoadRows( rowElem, child );
        }

        internal DataSetMapper Mapper {
            get {
                return mapper;
            }
        }

        internal void OnDataRowCreated( object oDataSet, DataRow row ) {
            Debug.Assert( row.RowState == DataRowState.Detached );
            OnNewRow( row );
        }

        internal void OnClearCalled( object oDataSet, DataTable table ) {
            throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_Clear ) );
        }

        internal void OnDataRowCreatedSpecial( object oDataSet, DataRow row ) {
            Debug.Assert( row.RowState == DataRowState.Detached );

            // Register the regular events and un-register this one
            Bind(true);
            // Pass the event to the regular listener
            OnNewRow( row );
        }
        // Called when a new DataRow is created
        internal void OnNewRow( DataRow row ) {
            Debug.Assert( row.Element == null );
            // Allow New state also because we are calling this function from
            Debug.Assert( row.RowState == DataRowState.Detached );

            AttachBoundElementToDataRow( row );
        }

        private XmlBoundElement AttachBoundElementToDataRow( DataRow row ) {
            Debug.Assert( row.Element == null );
            DataTable table = row.Table;
            // We shoould NOT call CreateElement here, since CreateElement will create and attach a new DataRow to the element
            XmlBoundElement rowElement = new XmlBoundElement( string.Empty, table.EncodedTableName, table.Namespace, this );
            rowElement.IsEmpty = false;
            Bind( row, rowElement );
            rowElement.ElementState = ElementState.Defoliated;
            return rowElement;
        }

        private bool NeedXSI_NilAttr( DataRow row ) {
            DataTable tb = row.Table;
            Debug.Assert( tb != null ) ;
            if ( tb.xmlText == null )
                return false;
            object value = row[tb.xmlText];
            return ( Convert.IsDBNull( value ) );
        }

        private void OnAddRow( DataRow row ) {
            // Xml operations in this func should not trigger ROM operations
            Debug.Assert( this.ignoreXmlEvents == true );

            XmlBoundElement rowElement = (XmlBoundElement)(GetElementFromRow( row ));
            Debug.Assert( rowElement != null );

            if ( NeedXSI_NilAttr( row ) && !rowElement.IsFoliated )
                //we need to foliate it because we need to add one more attribute xsi:nil = true;
                ForceFoliation( rowElement, AutoFoliationState );

            Debug.Assert( rowElement != null );
            DataRow rowDocElem = GetRowFromElement( this.DocumentElement );
            if ( rowDocElem != null ) {
                DataRow parentRow = GetNestedParent( row );
                if ( parentRow == null )
                    DemoteDocumentElement();
            }
            EnsureDocumentElement().AppendChild( rowElement );

            // Move the children of the row under
            FixNestedChildren( row, rowElement );
            OnNestedParentChange( row, rowElement, null );
        }

        private void OnColumnValueChanged( DataRow row, DataColumn col, XmlBoundElement rowElement ) {
            // 
            if ( IsNotMapped(col) )
                goto lblDoNestedRelationSync;

            object value = row[col];

            if ( col.ColumnMapping == MappingType.SimpleContent && Convert.IsDBNull( value ) && !rowElement.IsFoliated )
                ForceFoliation( rowElement, ElementState.WeakFoliation);
            else {
                // no need to sync if not foliated
                if ( !IsFoliated( rowElement ) ) {
#if DEBUG
                    // If the new value is null, we should be already foliated if there is a DataPointer that points to the column
                    // (see OnRowChanging, case DataRowAction.Change)
                    if ( Convert.IsDBNull( row[col, DataRowVersion.Current] ) ) {
                        try {
                            if ( pointers.Count > 0 ) {
                                object pointer = null;
                                foreach( DictionaryEntry entry in pointers ) {
                                    pointer = entry.Value;
                                    Debug.Assert( (pointer != null) && ! ((IXmlDataVirtualNode)pointer).IsOnColumn( col ) );
                                }
                            }
                        }
                        catch (Exception e) {
                            // 
                            if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) {
                                throw;
                            }
                            // We may get an exception if we are in foreach and a new pointer has been added to this.pointers. When this happens, we will skip this check and ignore the exceptions
                        }
                    }
#endif
                    goto lblDoNestedRelationSync;
                }
            }

            if ( IsTextOnly( col ) ) {
                if ( Convert.IsDBNull( value ) ) {
                    value = String.Empty;
                    //make sure that rowElement has Attribute xsi:nil and its value is true
                    XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL);
                    if ( attr == null ) {
                        attr = CreateAttribute( XSI, Keywords.XSI_NIL, Keywords.XSINS );
                        attr.Value = Keywords.TRUE;
                        rowElement.SetAttributeNode( attr );
                        this.bHasXSINIL = true;
                    }
                    else
                        attr.Value = Keywords.TRUE;
                }
                else {
                    //make sure that if rowElement has Attribute xsi:nil, its value is false
                    XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL);
                    if ( attr != null )
                        attr.Value = Keywords.FALSE;
                }
                ReplaceInitialChildText( rowElement, col.ConvertObjectToXml( value ) );
                goto lblDoNestedRelationSync;
            }

            // update the attribute that maps to the column
            bool fFound = false;

            // Find the field node and set it's value
            if (col.ColumnMapping == MappingType.Attribute) {
                foreach( XmlAttribute attr in rowElement.Attributes ) {
                    if ( attr.LocalName == col.EncodedColumnName && attr.NamespaceURI == col.Namespace ) {
                        if ( Convert.IsDBNull( value ) ) {
                            attr.OwnerElement.Attributes.Remove( attr );
                        }
                        else {
                            attr.Value = col.ConvertObjectToXml( value );
                        }
                        fFound = true;
                        break;
                    }
                }

                // create new attribute if we didn't find one.
                if ( !fFound && ! Convert.IsDBNull( value ) ) {
                    rowElement.SetAttribute( col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml( value ) );
                }
            }
            else {
                // update elements that map to the column...
                RegionIterator iter = new RegionIterator( (XmlBoundElement)rowElement );
                bool fMore = iter.Next();
                while ( fMore ) {
                    if ( iter.CurrentNode.NodeType == XmlNodeType.Element ) {
                        XmlElement e = (XmlElement) iter.CurrentNode;
                        Debug.Assert( e != null );
                        //we should skip the subregion
                        XmlBoundElement be = e as XmlBoundElement;
                        if ( be != null && be.Row != null ) {
                            fMore = iter.NextRight(); //skip over the sub-region
                            continue;
                        }
                        if ( e.LocalName == col.EncodedColumnName && e.NamespaceURI == col.Namespace ) {
                            fFound = true;
                            if ( Convert.IsDBNull( value ) ) {
                                PromoteNonValueChildren( e );
                                fMore = iter.NextRight();
                                e.ParentNode.RemoveChild( e );
                                // keep looking for more matching elements
                                continue;
                            }
                            else {
                                ReplaceInitialChildText( e, col.ConvertObjectToXml( value ) );
                                //make sure that if the Element has Attribute xsi:nil, its value is false
                                XmlAttribute attr = e.GetAttributeNode(XSI_NIL);
                                if ( attr != null )
                                    attr.Value = Keywords.FALSE;
                                // no need to look any further.
                                goto lblDoNestedRelationSync;
                            }
                        }
                    }
                    fMore = iter.Next();
                }

                // create new element if we didn't find one.
                if ( !fFound && ! Convert.IsDBNull( value ) ) {
                    XmlElement newElem = new XmlBoundElement( string.Empty, col.EncodedColumnName, col.Namespace, this );
                    newElem.AppendChild( CreateTextNode( col.ConvertObjectToXml( value ) ) );

                    XmlNode elemBefore = GetColumnInsertAfterLocation( row, col, rowElement );
                    if ( elemBefore != null ) {
                        rowElement.InsertAfter( newElem, elemBefore );
                    }
                    else if ( rowElement.FirstChild != null ) {
                        rowElement.InsertBefore( newElem, rowElement.FirstChild );
                    }
                    else {
                        rowElement.AppendChild( newElem );
                    }
                }
            }
lblDoNestedRelationSync:
            // Change the XML to conform to the (potentially) change in parent nested relation
            DataRelation relation = GetNestedParentRelation(row);
            if ( relation != null ) {
                Debug.Assert( relation.ChildTable == row.Table );
                if ( relation.ChildKey.ContainsColumn( col ) )
                    OnNestedParentChange( row, rowElement, col );
            }
        }

        private void OnColumnChanged( object sender, DataColumnChangeEventArgs args ) {
            // You should not be able to make DataRow field changes if the DataRow is deleted
            Debug.Assert( args.Row.RowState != DataRowState.Deleted );

            if ( ignoreDataSetEvents )
                return;

            bool wasIgnoreXmlEvents = ignoreXmlEvents;
            ignoreXmlEvents = true;
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;

            try {
                DataRow row     = args.Row;
                DataColumn col  = args.Column;
                object oVal     = args.ProposedValue;

                if ( row.RowState == DataRowState.Detached ) {
                    XmlBoundElement be = row.Element;
                    Debug.Assert( be != null );
                    if ( be.IsFoliated ) {
                        // Need to sync changes from ROM to DOM
                        OnColumnValueChanged( row, col, be );
                    }
                }
            }
            finally {
                IsFoliationEnabled = wasFoliationEnabled;
                ignoreXmlEvents = wasIgnoreXmlEvents;
            }
        }

        private void OnColumnValuesChanged( DataRow row, XmlBoundElement rowElement ) {
            Debug.Assert( row != null );
            Debug.Assert( rowElement != null );

            // If user has cascading relationships, then columnChangeList will contains the changed columns only for the last row beeing cascaded
            // but there will be multiple ROM events
            if ( columnChangeList.Count > 0 ) {
                if ( ((DataColumn)(columnChangeList[0])).Table == row.Table ) {
                    foreach( DataColumn c in columnChangeList )
                        OnColumnValueChanged( row, c, rowElement );
                }
                else {
                    foreach( DataColumn c in row.Table.Columns )
                        OnColumnValueChanged( row, c, rowElement );
                }
            }
            else {
                foreach( DataColumn c in row.Table.Columns )
                    OnColumnValueChanged( row, c, rowElement );
            }
            columnChangeList.Clear();
        }

        private void OnDeleteRow( DataRow row, XmlBoundElement rowElement ) {
            // IgnoreXmlEvents s/b on since we are manipulating the XML tree and we not want this to reflect in ROM view.
            Debug.Assert( this.ignoreXmlEvents == true );
            // Special case when rowElem is document element: we create a new docElem, move the current one as a child of
            // the new created docElem, then process as if the docElem is not a rowElem
            if ( rowElement == this.DocumentElement )
                DemoteDocumentElement();

            PromoteInnerRegions( rowElement );
            rowElement.ParentNode.RemoveChild( rowElement );
        }

        private void OnDeletingRow( DataRow row, XmlBoundElement rowElement ) {
            // Note that this function is beeing called even if ignoreDataSetEvents == true.

            // Foliate, so we can be able to preserve the nodes even if the DataRow has no longer values for the crtRecord.
            if ( IsFoliated( rowElement ) )
                return;

            bool wasIgnoreXmlEvents  = IgnoreXmlEvents;
            IgnoreXmlEvents          = true;
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled       = true;
            try {
                Foliate( rowElement );
            }
            finally {
                IsFoliationEnabled = wasFoliationEnabled;
                IgnoreXmlEvents    = wasIgnoreXmlEvents;
            }
        }

        private void OnFoliated( XmlNode node ) {
            while ( true ) {
                try {
                    if ( pointers.Count > 0 ) {
                        foreach( DictionaryEntry entry in pointers ) {
                            object pointer = entry.Value;
                            Debug.Assert( pointer != null );
                            ((IXmlDataVirtualNode)pointer).OnFoliated( node );
                        }
                    }
                    return;
                }
                catch (Exception e) {
                    // 
                    if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) {
                        throw;
                    }
                    // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop.
                    // Solution is to re-try OnFoliated.
                }
            }
            // You should never get here in regular cases
        }

        DataColumn FindAssociatedParentColumn( DataRelation relation, DataColumn childCol ) {
            DataColumn[] columns = relation.ChildKey.ColumnsReference;
            for (int i = 0; i < columns.Length; i++) {
                if ( childCol == columns[i] )
                    return relation.ParentKey.ColumnsReference[i];
            }
            return null;
        }

        // Change the childElement position in the tree to conform to the parent nested relationship in ROM
        private void OnNestedParentChange(DataRow child, XmlBoundElement childElement, DataColumn childCol) {
            Debug.Assert( child.Element == childElement && childElement.Row == child );
            // This function is (and s/b) called as a result of ROM changes, therefore XML changes done here should not be sync-ed to ROM
            Debug.Assert( ignoreXmlEvents == true );
#if DEBUG
            // In order to check that this move does not change the connected/disconnected state of the node
            bool fChildElementConnected = IsConnected( childElement );
#endif
            DataRow parentRowInTree;
            if ( childElement == this.DocumentElement || childElement.ParentNode == null )
                parentRowInTree = null;
            else
                parentRowInTree = GetRowFromElement( (XmlElement) childElement.ParentNode );
            DataRow parentRowInRelation = GetNestedParent(child);

            if ( parentRowInTree != parentRowInRelation ) {
                if ( parentRowInRelation != null ) {
                    // 

                    XmlElement newParent = GetElementFromRow( parentRowInRelation );
                    newParent.AppendChild( childElement );
                }
                else {
                    // no parent? Maybe the parentRow is during changing or childCol is the ID is set to null ( detached from the parent row ).
                    DataRelation relation = GetNestedParentRelation(child);
                    if ( childCol == null || relation == null || Convert.IsDBNull(child[childCol]) ) {
                        EnsureNonRowDocumentElement().AppendChild( childElement );
                    }
                    else {
                        DataColumn colInParent = FindAssociatedParentColumn( relation, childCol );
                        Debug.Assert( colInParent != null );
                        object comparedValue = colInParent.ConvertValue(child[childCol]);
                        if (parentRowInTree.tempRecord != -1 && colInParent.CompareValueTo( parentRowInTree.tempRecord, comparedValue ) != 0 ) {
                            EnsureNonRowDocumentElement().AppendChild( childElement );
                        }
                        //else do nothing because its original parentRowInRelation will be changed so that this row will still be its child
                    }
                }
            }
#if DEBUG
            // We should not have changed the connected/disconnected state of the node (since the row state did not change) -- IOW if the original childElem was in dis-connected
            // state and corresponded to a detached/deleted row, by adding it to the main tree we become inconsistent (since we have now a deleted/detached row in the main tree)
            // Same goes when we remove a node from connected tree to make it a child of a row-node corresponding to a non-live row.
            Debug.Assert( fChildElementConnected == IsConnected( childElement ) );
            Debug.Assert( IsRowLive( child ) ? IsConnected( childElement ) : ! IsConnected( childElement ) );
#endif
        }

        private void OnNodeChanged( object sender, XmlNodeChangedEventArgs args ) {
            if ( ignoreXmlEvents )
                return;

            bool wasIgnoreDataSetEvents = ignoreDataSetEvents;
            bool wasIgnoreXmlEvents     = ignoreXmlEvents;
            bool wasFoliationEnabled    = IsFoliationEnabled;
            ignoreDataSetEvents = true;
            ignoreXmlEvents     = true;
            IsFoliationEnabled  = false;
            bool fEnableCascading = DataSet.fEnableCascading;
            DataSet.fEnableCascading = false;

            try {
                // okay to allow text node value changes when bound.
                XmlBoundElement rowElement = null;

                Debug.Assert( DataSet.EnforceConstraints == false );

                if ( mapper.GetRegion( args.Node, out rowElement ) ) {
                    SynchronizeRowFromRowElement( rowElement );
                }
            }
            finally {
                ignoreDataSetEvents = wasIgnoreDataSetEvents;
                ignoreXmlEvents     = wasIgnoreXmlEvents;
                IsFoliationEnabled  = wasFoliationEnabled;
                DataSet.fEnableCascading = fEnableCascading;
            }
        }

        private void OnNodeChanging( object sender, XmlNodeChangedEventArgs args ) {
            if( ignoreXmlEvents )
                return;
            if ( DataSet.EnforceConstraints != false )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) );
        }


        private void OnNodeInserted( object sender, XmlNodeChangedEventArgs args ) {
            if ( ignoreXmlEvents )
                return;

            bool wasIgnoreDataSetEvents = ignoreDataSetEvents;
            bool wasIgnoreXmlEvents     = ignoreXmlEvents;
            bool wasFoliationEnabled    = IsFoliationEnabled;
            ignoreDataSetEvents = true;
            ignoreXmlEvents     = true;
            IsFoliationEnabled  = false;

            Debug.Assert( DataSet.EnforceConstraints == false );

            bool fEnableCascading = DataSet.fEnableCascading;
            DataSet.fEnableCascading = false;

            try {
                // Handle both new node inserted and 2nd part of a move operation.
                // 
                XmlNode node = args.Node;
                XmlNode oldParent = args.OldParent;
                XmlNode newParent = args.NewParent;

                // The code bellow assumes a move operation is fired by DOM in 2 steps: a Remvoe followed by an Insert - this is the 2nd part, the Insert.
                Debug.Assert( oldParent == null );
                if ( IsConnected( newParent ) ) {
                    // Inserting a node to connected tree
                    OnNodeInsertedInTree( node );
                }
                else {
                    // Inserting a node to disconnected tree
                    OnNodeInsertedInFragment( node );
                }
            }
            finally {
                ignoreDataSetEvents = wasIgnoreDataSetEvents;
                ignoreXmlEvents     = wasIgnoreXmlEvents;
                IsFoliationEnabled  = wasFoliationEnabled;
                DataSet.fEnableCascading = fEnableCascading;
            }

        }

        private void OnNodeInserting( object sender, XmlNodeChangedEventArgs args ) {
            if ( ignoreXmlEvents )
                return;
            if ( DataSet.EnforceConstraints != false )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) );
        }


        private void OnNodeRemoved( object sender, XmlNodeChangedEventArgs args ) {
            if ( ignoreXmlEvents )
                return;

            bool wasIgnoreDataSetEvents = ignoreDataSetEvents;
            bool wasIgnoreXmlEvents     = ignoreXmlEvents;
            bool wasFoliationEnabled    = IsFoliationEnabled;
            ignoreDataSetEvents = true;
            ignoreXmlEvents     = true;
            IsFoliationEnabled  = false;

            Debug.Assert( DataSet.EnforceConstraints == false );

            bool fEnableCascading = DataSet.fEnableCascading;
            DataSet.fEnableCascading = false;

            try {
                XmlNode node = args.Node;
                XmlNode oldParent = args.OldParent;
                Debug.Assert( args.NewParent == null );

                if ( IsConnected( oldParent ) ) {
                    // Removing from connected tree to disconnected tree
                    OnNodeRemovedFromTree( node, oldParent );
                }
                else {
                    // Removing from disconnected tree to disconnected tree: just sync the old region
                    OnNodeRemovedFromFragment( node, oldParent );
                }
            }
            finally {
                ignoreDataSetEvents = wasIgnoreDataSetEvents;
                ignoreXmlEvents     = wasIgnoreXmlEvents;
                IsFoliationEnabled  = wasFoliationEnabled;
                DataSet.fEnableCascading = fEnableCascading;
            }
        }

        private void OnNodeRemoving( object sender, XmlNodeChangedEventArgs args ) {
            if ( ignoreXmlEvents )
                return;
            if ( DataSet.EnforceConstraints != false )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) );
        }

        // Node was removed from connected tree to disconnected tree
        private void OnNodeRemovedFromTree( XmlNode node, XmlNode oldParent ) {
            XmlBoundElement oldRowElem;

            // Synchronize values from old region
            if ( mapper.GetRegion( oldParent, out oldRowElem ) )
                SynchronizeRowFromRowElement( oldRowElem );

            // Disconnect all regions, starting w/ node (if it is a row-elem)
            XmlBoundElement rowElem = node as XmlBoundElement;
            if ( rowElem != null && rowElem.Row != null )
                EnsureDisconnectedDataRow( rowElem );
            TreeIterator iter = new TreeIterator( node );
            for ( bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) {
                rowElem = (XmlBoundElement)(iter.CurrentNode);
                EnsureDisconnectedDataRow( rowElem );
            }

            // Assert that all sub-regions are disconnected
            AssertNonLiveRows( node );
        }
        // Node was removed from the disconnected tree to disconnected tree
        private void OnNodeRemovedFromFragment( XmlNode node, XmlNode oldParent ) {
            XmlBoundElement oldRowElem;

            if ( mapper.GetRegion( oldParent, out oldRowElem ) ) {
                // Sync the old region if it is not deleted
                DataRow row = oldRowElem.Row;
                // Since the old old region was disconnected, then the row can be only Deleted or Detached
                Debug.Assert( ! IsRowLive( row ) );
                if ( oldRowElem.Row.RowState == DataRowState.Detached )
                    SynchronizeRowFromRowElement( oldRowElem );
            }

            // Need to set nested for the sub-regions (if node is a row-elem, we need to set it just for itself)
            XmlBoundElement be = node as XmlBoundElement;
            if ( be != null && be.Row != null ) {
                Debug.Assert( ! IsRowLive( be.Row ) );
                SetNestedParentRegion( be, null );
            }
            else {
                // Set nested parent to null for all child regions
                TreeIterator iter = new TreeIterator( node );
                for ( bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() ) {
                    XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode);
                    SetNestedParentRegion( rowElemChild, null );
                }
            }

            // Assert that all sub-regions are disconnected
            AssertNonLiveRows( node );
        }


        private void OnRowChanged( object sender, DataRowChangeEventArgs args ) {
            if ( ignoreDataSetEvents )
                return;

            ignoreXmlEvents = true;
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;

            try {
                DataRow row = args.Row;
                XmlBoundElement rowElement = row.Element;
                // We should have an associated row-elem created when the DataRow was created (or at the load time)
                Debug.Assert( rowElement != null );

                switch ( args.Action ) {
                    case DataRowAction.Add:
                        // 








                        OnAddRow( row );
                        break;

                    case DataRowAction.Delete:
                        OnDeleteRow( row, rowElement );
                        break;

                    case DataRowAction.Rollback:
                        switch ( rollbackState ) {
                            case DataRowState.Deleted:
                                OnUndeleteRow( row, rowElement );
                                UpdateAllColumns( row, rowElement );
                                break;

                            case DataRowState.Added:
                                rowElement.ParentNode.RemoveChild( rowElement );
                                break;

                            case DataRowState.Modified:
                                OnColumnValuesChanged( row, rowElement );
                                break;
                        }
                        break;

                    case DataRowAction.Change:
                        OnColumnValuesChanged( row, rowElement );
                        break;

                    case DataRowAction.Commit:
                        if ( row.RowState == DataRowState.Detached ) {
                            //by now, all the descendent of the element that is not of this region should have been promoted already
                            rowElement.RemoveAll();
                        }
                        break;
                    default:
                        //Console.WriteLine("Other Event");
                        break;
                }
            }
            finally {
                IsFoliationEnabled = wasFoliationEnabled;
                ignoreXmlEvents = false;
            }
        }

        private void OnRowChanging( object sender, DataRowChangeEventArgs args ) {
            // We foliate the region each time the assocaited row gets deleted
            DataRow row = args.Row;
            if ( args.Action == DataRowAction.Delete && row.Element != null ) {
                OnDeletingRow( row, row.Element );
                return;
            }

            if ( ignoreDataSetEvents )
                return;

            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;

            try {
                ignoreXmlEvents = true;

                XmlElement rowElement = GetElementFromRow( row );

                int nRec1 = -1;
                int nRec2 = -1;

                if ( rowElement != null ) {
                    switch ( args.Action ) {
                        case DataRowAction.Add:
                            // DataRow is beeing added to the table (Table.Rows.Add is beeing called)
                            break;

                        case DataRowAction.Delete:
                            // DataRow is beeing deleted
                            //    - state transition from New (AKA PendingInsert) to Detached (AKA Created)
                            //    - state transition from Unchanged to Deleted (AKA PendingDelete)
                            //    - state transition from Modified (AKA PendingChange) to Delete (AKA PendingDelete)
                            Debug.Assert( false );  // This should have been handled above, irrespective of ignoreDataSetEvents value (true or false)
                            break;

                        case DataRowAction.Rollback:
                            // DataRow gets reverted to previous values (by calling DataRow.RejectChanges):
                            //    - state transition from Detached (AKA Created) to Detached (AKA Created)
                            //    - state transition from New (AKA PendingInsert) to Detached (AKA Created)
                            //    - state transition from Modified (AKA PendingChange) to Unchanged
                            //    - state transition from Deleted (AKA PendingDelete) to Unchanged
                            rollbackState = row.RowState;
                            switch ( rollbackState ) {
                                case DataRowState.Deleted:
                                    break;

                                case DataRowState.Detached:
                                    break;

                                case DataRowState.Added:
                                    break;

                                case DataRowState.Modified:
                                    columnChangeList.Clear();
                                    nRec1 = row.GetRecordFromVersion(DataRowVersion.Original);
                                    nRec2 = row.GetRecordFromVersion(DataRowVersion.Current);
                                    foreach( DataColumn c in row.Table.Columns ) {
                                        if ( !IsSame( c, nRec1, nRec2 ) )
                                            columnChangeList.Add(c);
                                    }
                                    break;

                            }
                            break;

                        case DataRowAction.Change:
                            // A DataRow field is beeing changed
                            //    - state transition from New (AKA PendingInsert) to New (AKA PendingInsert)
                            //    - state transition from Unchanged to Modified (AKA PendingChange)
                            //    - state transition from Modified (AKA PendingChange) to Modified (AKA PendingChange)
                            //
                            columnChangeList.Clear();
                            nRec1 = row.GetRecordFromVersion( DataRowVersion.Proposed );
                            nRec2 = row.GetRecordFromVersion( DataRowVersion.Current );
                            foreach( DataColumn c in row.Table.Columns ) {
                                object proposedValue = row[c, DataRowVersion.Proposed];
                                object currentValue  = row[c, DataRowVersion.Current];
                                // Foliate if proposedValue is DBNull; this way the DataPointer objects will point to a disconnected fragment after
                                // the DBNull value is beeing set
                                if ( Convert.IsDBNull( proposedValue ) && ! Convert.IsDBNull( currentValue ) ) {
                                    // Foliate only for non-hidden columns (since hidden cols are not represented in XML)
                                    if ( c.ColumnMapping != MappingType.Hidden )
                                        FoliateIfDataPointers( row, rowElement );
                                }
                                if ( !IsSame( c, nRec1, nRec2 ) )
                                    columnChangeList.Add(c);
                            }
                            break;

                        case DataRowAction.Commit:
                            break;
                    }
                }
            }
            finally {
                ignoreXmlEvents = false;
                IsFoliationEnabled = wasFoliationEnabled;
            }
        }

        private void OnDataSetPropertyChanging( object oDataSet, PropertyChangedEventArgs args ) {
            if ( args.PropertyName == "DataSetName" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNameChange ) );
            // 
        }
        private void OnColumnPropertyChanging( object oColumn, PropertyChangedEventArgs args ) {
            if ( args.PropertyName == "ColumnName" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnNameChange ) );
            if ( args.PropertyName == "Namespace" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnNamespaceChange ) );
            if ( args.PropertyName == "ColumnMapping" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnMappingChange ) );
        }
        private void OnTablePropertyChanging( object oTable, PropertyChangedEventArgs args ) {
            if ( args.PropertyName == "TableName" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_TableNameChange ) );
            if ( args.PropertyName == "Namespace" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_TableNamespaceChange ) );
        }
        private void OnTableColumnsChanging( object oColumnsCollection, CollectionChangeEventArgs args ) {
            // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh
            // args.Element is one of either the column (for Add and Remove actions or null, if the entire colection of columns is changing)

            // Disallow changing the columns collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode)
            throw new InvalidOperationException(  Res.GetString(Res.DataDom_TableColumnsChange ) );
        }

        private void OnDataSetTablesChanging( object oTablesCollection, CollectionChangeEventArgs args ) {
            // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh
            // args.Element is a table (dont know if it can be null: 

            // Disallow changing the tables collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode)
            throw new InvalidOperationException(  Res.GetString(Res.DataDom_DataSetTablesChange ) );
        }

        private void OnDataSetRelationsChanging( object oRelationsCollection, CollectionChangeEventArgs args ) {
            // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh
            // args.Element is a DataRelation (dont know if it can be null: 

            // Disallow changing the tables collection if there is data loaded and there are nested relationship that are added/refreshed
            DataRelation rel = (DataRelation)(args.Element);
            if ( rel != null && rel.Nested )
                throw new InvalidOperationException(  Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) );

            // If Add and Remove, we should already been throwing if .Nested == false
            Debug.Assert( ! (args.Action == CollectionChangeAction.Add || args.Action == CollectionChangeAction.Remove) || rel.Nested == false );
            if ( args.Action == CollectionChangeAction.Refresh ) {
                foreach ( DataRelation relTemp in (DataRelationCollection)oRelationsCollection ) {
                    if ( relTemp.Nested ) {
                        throw new InvalidOperationException(  Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) );
                    }
                }
            }
        }

        private void OnRelationPropertyChanging( object oRelationsCollection, PropertyChangedEventArgs args ) {
            if ( args.PropertyName == "Nested" )
                throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) );
        }

        private void OnUndeleteRow( DataRow row, XmlElement rowElement ) {
            XmlNode refRow;
            XmlElement parent;

            // make certain we weren't place somewhere else.
            if ( rowElement.ParentNode != null )
                rowElement.ParentNode.RemoveChild( rowElement );

            // Find the parent of RowNode to be inserted
            DataRow parentRowInRelation = GetNestedParent(row);
            if (parentRowInRelation == null) {
                parent = EnsureNonRowDocumentElement();
            }
            else
                parent = GetElementFromRow(parentRowInRelation);

            if ((refRow = GetRowInsertBeforeLocation(row, rowElement, parent)) != null)
                parent.InsertBefore(rowElement, refRow);
            else
                parent.AppendChild( rowElement );

            FixNestedChildren(row, rowElement);
        }

        // Promote the rowElemChild node/region after prevSibling node (as the next sibling)
        private void PromoteChild( XmlNode child, XmlNode prevSibling ) {
            // It makes no sense to move rowElemChild on the same level
            Debug.Assert( child.ParentNode != prevSibling.ParentNode );
            // prevSibling must have a parent, since we want to add a sibling to it
            Debug.Assert( prevSibling.ParentNode != null );
            Debug.Assert( IsFoliationEnabled == false );
            Debug.Assert( IgnoreXmlEvents == true );
            // Should not insert after docElem node
            Debug.Assert( prevSibling != this.DocumentElement );

            if ( child.ParentNode != null )
                child.ParentNode.RemoveChild( child );

            Debug.Assert( child.ParentNode == null );
            prevSibling.ParentNode.InsertAfter( child, prevSibling );
        }

        // Promote child regions under parent as next siblings of parent
        private void PromoteInnerRegions( XmlNode parent ) {
            Debug.Assert( parent != null );
            Debug.Assert( parent.NodeType != XmlNodeType.Attribute );   // We need to get get the grand-parent region
            Debug.Assert( parent != DocumentElement );                  // We cannot promote children of the DocumentElement

            XmlNode prevSibling = parent;
            XmlBoundElement parentRegionRowElem;
            mapper.GetRegion( parent.ParentNode, out parentRegionRowElem );

            TreeIterator iter = new TreeIterator( parent );
            bool fMore = iter.NextRowElement();
            while ( fMore ) {
                Debug.Assert( iter.CurrentNode is XmlBoundElement && ((XmlBoundElement)(iter.CurrentNode)).Row != null );
                XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode);
                fMore = iter.NextRightRowElement();
                PromoteChild( rowElemChild, prevSibling );
                SetNestedParentRegion( rowElemChild, parentRegionRowElem );
            }
        }

        private void PromoteNonValueChildren( XmlNode parent ) {
            Debug.Assert( parent != null );
            XmlNode prevSibling = parent;
            XmlNode child = parent.FirstChild;
            bool bTextLikeNode = true;
            XmlNode nextSibling = null;
            while ( child != null ) {
                nextSibling = child.NextSibling;                
                if (!bTextLikeNode || !IsTextLikeNode(child)) {
                    bTextLikeNode = false;
                    nextSibling = child.NextSibling;
                    PromoteChild( child, prevSibling );
                    prevSibling = child;
                }
                child = nextSibling;
            }
        }

        private void RemoveInitialTextNodes( XmlNode node ) {
            while ( node != null && IsTextLikeNode( node ) ) {
                XmlNode sibling = node.NextSibling;
                node.ParentNode.RemoveChild( node );
                node = sibling;
            }
        }

        private void ReplaceInitialChildText( XmlNode parent, string value ) {
            XmlNode n = parent.FirstChild;

            // don't consider whitespace when replacing initial text
            while ( n != null && n.NodeType == XmlNodeType.Whitespace )
                n = n.NextSibling;

            if ( n != null ) {
                if ( n.NodeType == XmlNodeType.Text )
                    n.Value = value;
                else
                    n = parent.InsertBefore( CreateTextNode( value ), n );
                RemoveInitialTextNodes( n.NextSibling );
            }
            else {
                parent.AppendChild( CreateTextNode( value ) );
            }
        }

        internal XmlNode SafeFirstChild( XmlNode n ) {
            XmlBoundElement be = n as XmlBoundElement;
            if ( be != null )
                return be.SafeFirstChild;
            else
                //other type of node should be already foliated.
                return n.FirstChild;
        }

        internal XmlNode SafeNextSibling( XmlNode n ) {
            XmlBoundElement be = n as XmlBoundElement;
            if ( be != null )
                return be.SafeNextSibling;
            else
                //other type of node should be already foliated.
                return n.NextSibling;
        }

        internal XmlNode SafePreviousSibling( XmlNode n ) {
            XmlBoundElement be = n as XmlBoundElement;
            if ( be != null )
                return be.SafePreviousSibling;
            else
                //other type of node should be already foliated.
                return n.PreviousSibling;
        }

        internal static void SetRowValueToNull( DataRow row, DataColumn col ) {
            Debug.Assert( col.ColumnMapping != MappingType.Hidden );
            Debug.Assert( row.Table.DataSet.EnforceConstraints == false );

            // 
            if ( ! ( row.IsNull( col ) ) )
                row[ col ] = Convert.DBNull;
        }

        internal static void SetRowValueFromXmlText( DataRow row, DataColumn col, string xmlText ) {
            Debug.Assert( xmlText != null );
            Debug.Assert( row.Table.DataSet.EnforceConstraints == false );
            object oVal;
            try {
                oVal = col.ConvertXmlToObject( xmlText );
                // This func does not set the field value to null - call SetRowValueToNull in order to do so
                Debug.Assert( oVal != null && ! ( oVal is DBNull ) );
            }
            catch (Exception e) {
                // 
                if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) {
                        throw;
                }
                // Catch data-type errors and set ROM to Unspecified value
                SetRowValueToNull( row, col );
                return;
            }

            if ( ! oVal.Equals( row[col] ) )
                row[ col ] = oVal;
        }

        private void SynchronizeRowFromRowElement( XmlBoundElement rowElement ) {
            SynchronizeRowFromRowElement( rowElement, null );
        }
        // Sync row fields w/ values from rowElem region.
        // If rowElemList is != null, all subregions of rowElem are appended to it.
        private void SynchronizeRowFromRowElement( XmlBoundElement rowElement, ArrayList rowElemList ) {
            DataRow row = rowElement.Row;
            Debug.Assert( row != null );

            // No synchronization needed for deleted rows
            if ( row.RowState == DataRowState.Deleted )
                return;

            row.BeginEdit();
#if DEBUG
            try {
#endif
                SynchronizeRowFromRowElementEx( rowElement, rowElemList );
#if DEBUG
            }
            catch {
                // We should not get any exceptions because we always handle data-type conversion
                Debug.Assert( false );
                throw;
            }
#endif
#if DEBUG
            try {
#endif
                row.EndEdit();
#if DEBUG
            }
            catch {
                // We should not get any exceptions because DataSet.EnforceConstraints should be always off
                // 
                Debug.Assert( false );
                throw;
            }
#endif
        }
        private void SynchronizeRowFromRowElementEx( XmlBoundElement rowElement, ArrayList rowElemList ) {
            Debug.Assert( rowElement != null );
            Debug.Assert( rowElement.Row != null );
            Debug.Assert( this.DataSet.EnforceConstraints == false );

            DataRow row = rowElement.Row;
            Debug.Assert( row != null );
            DataTable table = row.Table;

            // if not foliated, already synch'd
//            if ( !IsFoliated(rowElement) )
//                return;
            //Debug.Assert( IsFoliated(rowElement) ); // If foliated we should not get the event (should be handled directly by DataPointer)

            Hashtable foundColumns = new Hashtable();
            string xsi_attrVal = string.Empty;

            RegionIterator iter = new RegionIterator( rowElement );
            bool fMore;
            // If present, fill up the TextOnly column
            DataColumn column = GetTextOnlyColumn( row );
            if ( column != null ) {
                foundColumns[column] = column;
                string value;
                fMore = iter.NextInitialTextLikeNodes( out value );
                if ( value.Length == 0 && ( ( (xsi_attrVal = rowElement.GetAttribute(XSI_NIL) ) == "1" ) || xsi_attrVal == "true" ) )
                    row[column] = Convert.DBNull;
                else
                    SetRowValueFromXmlText( row, column, value );
            }
            else
                fMore = iter.Next();

            // Fill up the columns mapped to an element
            while ( fMore ) {
                XmlElement e = iter.CurrentNode as XmlElement;
                if ( e == null ) {
                    fMore = iter.Next();
                    continue;
                }

                XmlBoundElement be = e as XmlBoundElement;
                if ( be != null && be.Row != null ) {
                    if ( rowElemList != null )
                        rowElemList.Add( e );
                    // Skip over sub-regions
                    fMore = iter.NextRight();
                    continue;
                }

                DataColumn c = mapper.GetColumnSchemaForNode( rowElement, e );
                if ( c != null ) {
                    Debug.Assert( c.Table == row.Table );
                    if ( foundColumns[c] == null ) {
                        foundColumns[c] = c;
                        string value;
                        fMore = iter.NextInitialTextLikeNodes( out value );
                        if ( value.Length == 0 && ( ( (xsi_attrVal = e.GetAttribute(XSI_NIL) ) == "1" ) || xsi_attrVal == "true" ) )
                            row[c] = Convert.DBNull;
                        else
                            SetRowValueFromXmlText( row, c, value );
                        continue;
                    }
                }

                fMore = iter.Next();
            }

            //
            // Walk the attributes to find attributes that map to columns.
            //
            foreach( XmlAttribute attr in rowElement.Attributes ) {
                DataColumn c = mapper.GetColumnSchemaForNode( rowElement, attr );

                if ( c != null ) {
                    if ( foundColumns[c] == null ) {
                        foundColumns[c] = c;
                        SetRowValueFromXmlText( row, c, attr.Value );
                    }
                }
            }

            // Null all columns values that aren't represented in the tree
            foreach( DataColumn c in row.Table.Columns ) {
                if ( foundColumns[c] == null && !IsNotMapped(c) ) {
                    if (!c.AutoIncrement)
                        SetRowValueToNull( row, c );
                    else
                        c.Init(row.tempRecord);
                }
            }
        }

        private void UpdateAllColumns( DataRow row, XmlBoundElement rowElement ) {
            foreach( DataColumn c in row.Table.Columns ) {
                OnColumnValueChanged( row, c, rowElement );
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the XmlDataDocument class.
        ///    </para>
        /// </devdoc>
        public XmlDataDocument(): base(new XmlDataImplementation()) {
            Init();
            AttachDataSet( new DataSet() );
            this.dataSet.EnforceConstraints = false;
        }

        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the XmlDataDocument class with the specified
        ///       DataSet.
        ///    </para>
        /// </devdoc>
        public XmlDataDocument( DataSet dataset ): base(new XmlDataImplementation()) {
            Init( dataset );
        }

        internal XmlDataDocument( XmlImplementation imp ) : base( imp ) {
        }

        private void Init() {
            this.pointers = new Hashtable();
            this.countAddPointer = 0;
            this.columnChangeList = new ArrayList();
            this.ignoreDataSetEvents = false;
            this.isFoliationEnabled = true;
            this.optimizeStorage = true;
            this.fDataRowCreatedSpecial = false;
            autoFoliationState = ElementState.StrongFoliation;
            fAssociateDataRow = true; //this needs to be true for newly created elements should have associated datarows
            mapper = new DataSetMapper();
            this.foliationLock = new object();
            this.ignoreXmlEvents = true;
            this.attrXml = CreateAttribute( "xmlns", "xml", XPathNodePointer.s_strReservedXmlns );
            this.attrXml.Value = XPathNodePointer.s_strReservedXml;
            this.ignoreXmlEvents = false;
        }

        private void Init( DataSet ds ) {
            if ( ds == null )
                throw new ArgumentException(Res.GetString(Res.DataDom_DataSetNull));
            Init();
            if ( ds.FBoundToDocument )
                throw new ArgumentException( Res.GetString(Res.DataDom_MultipleDataSet) );
            ds.FBoundToDocument = true;
            this.dataSet = ds;
            Bind(true);
        }

        private bool IsConnected( XmlNode node ) {
            while ( true ) {
                if ( node == null )
                    return false;
                if ( node == this )
                    return true;

                XmlAttribute attr = node as XmlAttribute;
                if ( attr != null )
                    node = attr.OwnerElement;
                else
                    node = node.ParentNode;
            }
        }
        private bool IsRowLive( DataRow row ) {
            return ( row.RowState & ( DataRowState.Added | DataRowState.Unchanged | DataRowState.Modified ) ) != 0;
        }
        private static void SetNestedParentRow( DataRow childRow, DataRow parentRow ) {
            DataRelation rel = GetNestedParentRelation( childRow );
            //we should not set this row's parentRow if the table doesn't match.
            if ( rel != null ) {
                if ( parentRow == null || rel.ParentKey.Table != parentRow.Table )
                    childRow.SetParentRow( null, rel );
                else
                   childRow.SetParentRow( parentRow, rel );
            }
        }

        // A node (node) was inserted into the main tree (connected) from oldParent==null state
        private void OnNodeInsertedInTree( XmlNode node ) {
            XmlBoundElement be;
            ArrayList rowElemList = new ArrayList();
            if ( mapper.GetRegion( node, out be ) ) {
                // 
                if ( be == node ) {
                    OnRowElementInsertedInTree( be, rowElemList );
                }
                else {
                    OnNonRowElementInsertedInTree( node, be, rowElemList );
                }
            }
            else {
                // We only need to sync the embedded sub-regions
                TreeIterator iter = new TreeIterator( node );
                for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() )
                    rowElemList.Add( iter.CurrentNode );
            }

            // Process subregions, so they make transition from disconnected to connected tree
            while ( rowElemList.Count > 0 ) {
                Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement);
                XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]);
                rowElemList.RemoveAt( 0 );
                // Expect rowElem to have a DataTable schema, since it is a sub-region
                Debug.Assert( subRowElem != null );
                OnRowElementInsertedInTree( subRowElem, rowElemList );
            }

            // Assert that all sub-regions are assoc w/ "live" rows
            AssertLiveRows( node );
        }
        // "node" was inserting into a disconnected tree from oldParent==null state
        private void OnNodeInsertedInFragment( XmlNode node ) {
            XmlBoundElement be;
            if ( mapper.GetRegion( node, out be ) ) {
                if ( be == node ) {
                    Debug.Assert( ! IsRowLive( be.Row ) );
                    SetNestedParentRegion( be );
                }
                else {
                    ArrayList rowElemList = new ArrayList();
                    OnNonRowElementInsertedInFragment( node, be, rowElemList );
                    // Set nested parent for the 1st level subregions (they should already be associated w/ Deleted or Detached rows)
                    while ( rowElemList.Count > 0 ) {
                        Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement);
                        XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]);
                        rowElemList.RemoveAt( 0 );
                        SetNestedParentRegion( subRowElem, be );
                    }
                }

                // Check to make sure all sub-regions are disconnected
                AssertNonLiveRows( node );

                return;
            }

            // Nothing to do, since the node belongs to no region

            // Check to make sure all sub-regions are disconnected
            AssertNonLiveRows( node );
        }

        // A row-elem was inserted into the connected tree (connected) from oldParent==null state
        private void OnRowElementInsertedInTree( XmlBoundElement rowElem, ArrayList rowElemList ) {
            Debug.Assert( rowElem.Row != null );

            DataRow row = rowElem.Row;
            DataRowState rowState = row.RowState;

            switch( rowState ) {
            case DataRowState.Detached:
#if DEBUG
                try {
                    Debug.Assert( row.Table.DataSet.EnforceConstraints == false );
#endif
                    row.Table.Rows.Add( row );
                    SetNestedParentRegion( rowElem );
#if DEBUG
                }
                catch {
                    // We should not get any exceptions here
                    Debug.Assert( false );
                    throw;
                }
#endif
                // Add all sub-regions to the list if the caller needs this
                if ( rowElemList != null ) {
                    RegionIterator iter = new RegionIterator( rowElem );
                    for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() )
                        rowElemList.Add( iter.CurrentNode );
                }
                break;
            case DataRowState.Deleted:
#if DEBUG
                try {
                    Debug.Assert( row.Table.DataSet.EnforceConstraints == false );
#endif
                    // Change the row status to be alive (unchanged)
                    row.RejectChanges();
                    // Set ROM from XML
                    SynchronizeRowFromRowElement( rowElem, rowElemList );
                    // Set nested parent data row according to where is the row positioned in the tree
                    SetNestedParentRegion( rowElem );
#if DEBUG
                }
                catch {
                    // We should not get any exceptions here
                    Debug.Assert( false );
                    throw;
                }
#endif
                break;
            default:
                // Handle your case above
                // 
                Debug.Assert( false );
                break;
            }
            Debug.Assert( IsRowLive( rowElem.Row ) );
        }

        // Disconnect the DataRow associated w/ the rowElem region
        private void EnsureDisconnectedDataRow( XmlBoundElement rowElem ) {
            Debug.Assert( rowElem.Row != null );

            DataRow row = rowElem.Row;
            DataRowState rowState = row.RowState;

            switch( rowState ) {
            case DataRowState.Detached:
#if DEBUG
                try {
                    Debug.Assert( row.Table.DataSet.EnforceConstraints == false );
#endif
                    SetNestedParentRegion( rowElem );
#if DEBUG
                }
                catch {
                    // We should not get any exceptions here
                    Debug.Assert( false );
                    throw;
                }
#endif
                break;

            case DataRowState.Deleted:
                // Nothing to do: moving a region associated w/ a deleted row to another disconnected tree is a NO-OP.
                break;

            case DataRowState.Unchanged:
            case DataRowState.Modified:
                EnsureFoliation( rowElem, ElementState.WeakFoliation );
                row.Delete();
                break;

            case DataRowState.Added:
                EnsureFoliation( rowElem, ElementState.WeakFoliation );
                row.Delete();
                SetNestedParentRegion( rowElem );
                break;

            default:
                // Handle your case above
                // 
                Debug.Assert( false );
                break;
            }

            Debug.Assert( ! IsRowLive( rowElem.Row ) );
        }


        // A non-row-elem was inserted into the connected tree (connected) from oldParent==null state
        private void OnNonRowElementInsertedInTree( XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList ) {
            // non-row-elem is beeing inserted
            DataRow row = rowElement.Row;
            // Region should already have an associated data row (otherwise how was the original row-elem inserted ?)
            Debug.Assert( row != null );
            SynchronizeRowFromRowElement( rowElement );
            if ( rowElemList != null ) {
                TreeIterator iter = new TreeIterator( node );
                for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() )
                    rowElemList.Add( iter.CurrentNode );
            }
        }

        // A non-row-elem was inserted into disconnected tree (fragment) from oldParent==null state (i.e. was disconnected)
        private void OnNonRowElementInsertedInFragment( XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList ) {
            // non-row-elem is beeing inserted
            DataRow row = rowElement.Row;
            // Region should already have an associated data row (otherwise how was the original row-elem inserted ?)
            Debug.Assert( row != null );
            // Since oldParent == null, the only 2 row states should have been Detached or Deleted
            Debug.Assert( row.RowState == DataRowState.Detached || row.RowState == DataRowState.Deleted );

            if ( row.RowState == DataRowState.Detached )
                SynchronizeRowFromRowElementEx( rowElement, rowElemList );
            // Nothing to do if the row is deleted (there is no sync-ing from XML to ROM for deleted rows)
        }

        private void SetNestedParentRegion( XmlBoundElement childRowElem ) {
            Debug.Assert( childRowElem.Row != null );

            XmlBoundElement parentRowElem;
            mapper.GetRegion( childRowElem.ParentNode, out parentRowElem );
            SetNestedParentRegion( childRowElem, parentRowElem );
        }
        private void SetNestedParentRegion( XmlBoundElement childRowElem, XmlBoundElement parentRowElem ) {
            DataRow childRow = childRowElem.Row;
            if ( parentRowElem == null ) {
                SetNestedParentRow( childRow, null );
                return;
            }

            DataRow parentRow = parentRowElem.Row;
            Debug.Assert( parentRow != null );
            // We should set it only if there is a nested relationship between this child and parent regions
            DataRelation [] relations = childRow.Table.NestedParentRelations;
            if (relations.Length != 0 && relations[0].ParentTable == parentRow.Table ) // just backward compatable
                // 
                SetNestedParentRow( childRow, parentRow );
            else
                SetNestedParentRow( childRow, null );
        }

        internal static bool IsTextNode( XmlNodeType nt ) {
            switch( nt ) {
                case XmlNodeType.Text:
                case XmlNodeType.CDATA:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                    return true;
                default:
                    return false;
            }
        }

        /*
        internal static bool IsWhiteSpace(char ch) {
            switch ( ch ) {
                case '\u0009' :
                case '\u000a' :
                case '\u000d' :
                case '\u0020' :
                    return true;
                default :
                    return false;
            }
        }

        internal static bool IsOnlyWhitespace( string str ) {
            if (str != null) {
                for (int index = 0; index < str.Length; index ++) {
                    if (! IsWhiteSpace(str[index]))
                        return false;
                }
            }
            return true;
        }
        */

        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        protected override XPathNavigator CreateNavigator(XmlNode node) {
            Debug.Assert( node.OwnerDocument == this || node == this );
            if ( XPathNodePointer.xmlNodeType_To_XpathNodeType_Map[(int)(node.NodeType)] == -1 )
                return null;
            if ( IsTextNode( node.NodeType ) ) {
                XmlNode parent = node.ParentNode;
                if ( parent != null && parent.NodeType == XmlNodeType.Attribute )
                    return null;
                else {
#if DEBUG
                    //if current node is a text node, its parent node has to be foliated
                    XmlBoundElement be = node.ParentNode as XmlBoundElement;
                    if ( be != null )
                        Debug.Assert( be.IsFoliated );
#endif
                    XmlNode prevSib = node.PreviousSibling;
                    while ( prevSib != null && IsTextNode( prevSib.NodeType ) ) {
                        node = prevSib;
                        prevSib = SafePreviousSibling( node );
                    }
                }
            }
            return new DataDocumentXPathNavigator( this, node );
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private void AssertLiveRows( XmlNode node ) {
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;
            try {
                XmlBoundElement rowElement = node as XmlBoundElement;
                if ( rowElement != null && rowElement.Row != null )
                    Debug.Assert( IsRowLive( rowElement.Row ) );
                TreeIterator iter = new TreeIterator( node );
                for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) {
                    rowElement = iter.CurrentNode as XmlBoundElement;
                    Debug.Assert( rowElement.Row != null );
                    Debug.Assert( IsRowLive( rowElement.Row ) );
                }
            }
            finally {
                IsFoliationEnabled = wasFoliationEnabled;
            }
        }
        [System.Diagnostics.Conditional("DEBUG")]
        private void AssertNonLiveRows( XmlNode node ) {
            bool wasFoliationEnabled = IsFoliationEnabled;
            IsFoliationEnabled = false;
            try {
                XmlBoundElement rowElement = node as XmlBoundElement;
                if ( rowElement != null && rowElement.Row != null )
                    Debug.Assert( ! IsRowLive( rowElement.Row ) );
                TreeIterator iter = new TreeIterator( node );
                for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) {
                    rowElement = iter.CurrentNode as XmlBoundElement;
                    Debug.Assert( rowElement.Row != null );
                    Debug.Assert( ! IsRowLive( rowElement.Row ) );
                }
            }
            finally {
                IsFoliationEnabled = wasFoliationEnabled;
            }
        }

        public override XmlElement GetElementById( string elemId ) {
            throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_GetElementById ) );
        }
        public override XmlNodeList GetElementsByTagName(string name) {
            // Retrieving nodes from the returned nodelist may cause foliation which causes new nodes to be created,
            // so the System.Xml iterator will throw if this happens during iteration. To avoid this, foliate everything
            // before iteration, so iteration will not cause foliation (and as a result of this, creation of new nodes).
            XmlNodeList tempNodeList = base.GetElementsByTagName(name);

            int tempint = tempNodeList.Count;
            return tempNodeList;
        }

// Webdata 103397

//  after adding Namespace support foir datatable, DataSet does not guarantee that infered tabels would be in the same sequence as they rae in XML, because
//  of Namespace. if a table is in different namespace than its children and DataSet, that table would efinetely be added to DataSet after its children. Its By Design
// so in order to maintain backward compatability, we reorder the copy of the datatable collection and use it 
        private DataTable[] OrderTables(DataSet ds) {

            DataTable[] retValue = null;         
            if (ds == null ||ds.Tables.Count == 0) {
                retValue = new DataTable[0];
            }
            else if (TablesAreOrdered(ds)) {
                retValue =  new DataTable[ds.Tables.Count];
                ds.Tables.CopyTo(retValue, 0); 
		// XDD assumes PArent table exist before its child, if it does not we wont be handle the case
		// same as Everett
            }
            
            if (null == retValue) {
                retValue =  new DataTable[ds.Tables.Count];
                List<DataTable> tableList = new List<DataTable>(); 
                // first take the root tables that have no parent 
                foreach(DataTable dt in ds.Tables) {
                    if (dt.ParentRelations.Count == 0) {
                        tableList.Add(dt);
                    }
                }

                if (tableList.Count > 0) { // if we have some  table inside; 
                    foreach(DataTable dt in ds.Tables) {
                        if (IsSelfRelatedDataTable(dt)) {
                            tableList.Add(dt);
                        }
                    }
                    for(int readPos = 0 ; readPos < tableList.Count; readPos ++) {
                        Debug.Assert(tableList[readPos] != null, "Temp Array is not supposed to reach to null");
                        foreach(DataRelation r in  tableList[readPos].ChildRelations) {
                            DataTable childTable = r.ChildTable;
                            if (!tableList.Contains(childTable))
                                tableList.Add(childTable);
                        }
                    }
                    tableList.CopyTo(retValue);

                }
                else {//there will not be  any in case just if we have circular relation dependency, just copy as they are in tablecollection use CopyTo of the collection
                    ds.Tables.CopyTo(retValue, 0); 
                }
            }
            return retValue;        
        }
        private bool IsSelfRelatedDataTable(DataTable rootTable) {
            List<DataTable> tableList = new List<DataTable>(); 
            bool retValue = false;
            foreach(DataRelation r in  rootTable.ChildRelations) {
                DataTable childTable = r.ChildTable;
                if (childTable == rootTable) {
                    retValue = true;
                    break;
                }
                else if (!tableList.Contains(childTable)) {
                    tableList.Add(childTable);
                }
            }
            if (!retValue) {
                for(int counter = 0 ; counter < tableList.Count; counter++) {
                    foreach(DataRelation r in  tableList[counter].ChildRelations) {
                        DataTable childTable = r.ChildTable;
                        if (childTable == rootTable) {
                            retValue = true;
                            break;
                        }
                        else if (!tableList.Contains(childTable)) {
                            tableList.Add(childTable);
                        }
                    }
                    if (retValue){
                        break;
                    }
                }
            }
            return retValue;
        }
        private bool TablesAreOrdered(DataSet ds) {
            foreach(DataTable dt in ds.Tables){
                if (dt.Namespace != ds.Namespace) {
                    return false;
                }
            }
            return true;
        }
    }
}
